﻿using Fast;
using Fast.AspNetCoreMvc;
using Fast.ExceptionHandling;
using Fast.Extensions;
using Fast.NormalizeActionResult;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Mvc.Filters
{
    /// <summary>
    /// 异常拦截器
    /// </summary>
    [SkipScan]
    public class FastExceptionFilter : IAsyncExceptionFilter
    {
        private readonly IHttpStatusCodeFinder _httpStatusCodeFinder;
        private readonly IActionResultProvider _actionResultProvider;
        private readonly IExceptionMetadataConverter _exceptionMetadataConverter;
        public FastExceptionFilter(
            IHttpStatusCodeFinder httpStatusCodeFinder,
            IActionResultProvider actionResultProvider,
            IExceptionMetadataConverter exceptionMetadataConverter)
        {
            _httpStatusCodeFinder = httpStatusCodeFinder;
            _actionResultProvider = actionResultProvider;
            _exceptionMetadataConverter = exceptionMetadataConverter;
        }
        /// <summary>
        /// 异常拦截
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public Task OnExceptionAsync(ExceptionContext context)
        {
            if (context.ExceptionHandled) return Task.CompletedTask;

            var exceptionMetadata = _exceptionMetadataConverter.Convert(context.Exception);

            var exceptionMetadataBuilder = new StringBuilder();
            exceptionMetadataBuilder.AppendLine($"---------- {nameof(ExceptionMetadata)} ----------");
            exceptionMetadataBuilder.AppendLine(JsonConvert.SerializeObject(exceptionMetadata));

            var logger = context.GetService<ILogger<FastExceptionFilter>>(NullLogger<FastExceptionFilter>.Instance);
            logger.LogError(exceptionMetadataBuilder.ToString());
            logger.LogError(context.Exception, context.Exception.ToString());

            var normalizeActionResultFilter = context.HttpContext.RequestServices.GetService<NormalizeActionResultFilter>();
            if (normalizeActionResultFilter == null)
            {
                var content = $"{exceptionMetadata.Message}";
                if (!exceptionMetadata.ValidationErrors.IsNullOrEmpty())
                {
                    content += $"\r\n{JsonConvert.SerializeObject(exceptionMetadata.ValidationErrors)}";
                }
                context.Result = new ContentResult
                {
                    Content = content
                };
            }
            else
            {
                context.Result = _actionResultProvider.GetExceptionResult(context, exceptionMetadata);
                context.HttpContext.Response.StatusCode = (int)_httpStatusCodeFinder.GetStatusCode(context.HttpContext, context.Exception);
            }

            PrintToMiniProfiler(context.Exception);

            context.Exception = null;

            return Task.CompletedTask;
        }

        /// <summary>
        /// 打印错误到 MiniProfiler 中
        /// </summary>
        /// <param name="exception"></param>
        private void PrintToMiniProfiler(Exception exception)
        {
            // 获取异常堆栈
            var traceFrame = new StackTrace(exception, true).GetFrame(0);

            // 获取出错的文件名
            var exceptionFileName = traceFrame.GetFileName();

            // 获取出错的行号
            var exceptionFileLineNumber = traceFrame.GetFileLineNumber();

            // 打印错误文件名和行号
            if (!string.IsNullOrWhiteSpace(exceptionFileName) && exceptionFileLineNumber > 0)
            {
                App.PrintToMiniProfiler(MiniProfilerCategory.Error.GetDescription(), "Locator", $"{exceptionFileName}:line {exceptionFileLineNumber}", true);
            }

            // 打印完整的堆栈信息
            App.PrintToMiniProfiler(MiniProfilerCategory.Error.GetDescription(), "StackTrace", exception.ToString(), true);
        }
    }
}
